#include <algorithm>
#include <cstdio>
#include <queue>

#include <ctime>

#include "prologin.hh"

using namespace std;

const int oo = 1E9;
const int MOI = 1;
const int ADV = 0;

int id, adv;

struct Case
{
	int distBase[2], dejaVu;
	int prof[2], remontee[2];
	double utilite;
	int nbVoisins[2];
	Case()
	{
		distBase[ADV] = oo;
		distBase[MOI] = oo;
		utilite = 0;
		nbVoisins[ADV] = 0;
		nbVoisins[MOI] = 0;
		prof[ADV] = -1;
		prof[MOI] = -1;
		dejaVu = 0;
	}
	bool essentiel(int joueur)
	{
		return nbVoisins[joueur == id] > 1 && prof[joueur == id] == remontee[joueur == id];
	}
};

Case grille[TAILLE_TERRAIN][TAILLE_TERRAIN];

struct Position
{
	int x, y;
	Position(int x = 0, int y = 0) : x(x), y(y) {}
	Position operator+(const Position& droite) const
	{
		return Position(x + droite.x, y + droite.y);
	}
	bool withinBounds()
	{
		return (x >= 0 && x < TAILLE_TERRAIN && y >= 0 && y < TAILLE_TERRAIN && type_case(*this) != INTERDIT);
	}
	vector<Position> voisins();
	position pos()
	{
		position retour;
		retour.x = x;
		retour.y = y;
		return retour;
	}
	Case getGrille() const
	{
		return grille[x][y];
	}
	int newPlasma()
	{
		int plasma = 0;
		auto neighbours = this->voisins();
		for(auto voisin = neighbours.begin(); voisin != neighbours.end(); ++voisin)
			if(est_pulsar(voisin->pos()))
				plasma += plasmaRestant(*voisin);
		return plasma;
	}
};

Position dirs[4] = {Position(-1,0), Position(1,0), Position(0,-1), Position(0,1)};

vector<Position> Position::voisins()
{
	vector<Position> retour;
	for(int iDir = 0; iDir < 4; ++iDir)
		if((*this + dirs[iDir]).withinBounds())
			retour.push_back(*this + dirs[iDir]);
	return retour;
}

struct Chemin
{
	Position pos;
	int longueur;
	int score;
	Position depart;
	Chemin(Position pos = Position(-1,-1), int longueur = 0, int score = 0, Position depart = Position(-1,-1)) : pos(pos), longueur(longueur), score(score), depart(depart) {}
	Chemin operator + (const Chemin& droite) const
	{
		return Chemin(pos + droite.pos, longueur + droite.longueur, score + droite.score, (depart.x == -1) ? pos + droite.pos : depart);
	}
	bool operator < (const Chemin& droite) const
	{
		if(longueur + depart.getGrille().distBase[MOI] - score != droite.longueur + droite.depart.getGrille().distBase[MOI] - droite.score)
			return longueur + depart.getGrille().distBase[MOI] - score > droite.longueur + droite.depart.getGrille().distBase[MOI] - droite.score;
		if(longueur - score != droite.longueur - droite.score)
			return longueur - score > droite.longueur - droite.score;
		return longueur > droite.longueur;
	}
	vector<Chemin> voisins()
	{
		vector<Chemin> retour;
		for(int iDir = 0; iDir < 4; ++iDir)
			if((this->pos + dirs[iDir]).withinBounds())
				retour.push_back(*this + Chemin(dirs[iDir], 1));
		return retour;
	}
};

int idDejaVu = 0;

void partie_init()
{
	id = moi();
	adv = adversaire();
}

void jouer_tour()
{
	//auto debutTime = clock();
	for(int iAction = 0; iAction < 8 && points_action(); ++iAction)
	{
		idDejaVu = 0;
		for(int x = 0; x < TAILLE_TERRAIN; ++x)
			for(int y = 0; y < TAILLE_TERRAIN; ++y)
				grille[x][y] = Case();
		calculerDistBase(id);
		calculerDistBase(adv);
		auto base = ma_base();
		for(auto pos = base.begin(); pos != base.end(); ++pos)
		{
			auto voisins = Position(pos->x, pos->y).voisins();
			for(auto voisin = voisins.begin(); voisin != voisins.end(); ++voisin)
				if(est_tuyau(voisin->pos()))
					calculerEssentiel(*voisin, id, Position(-1, -1));
		}
		base = base_ennemie();
		for(auto pos = base.begin(); pos != base.end(); ++pos)
		{
			auto voisins = Position(pos->x, pos->y).voisins();
			for(auto voisin = voisins.begin(); voisin != voisins.end(); ++voisin)
				if(est_tuyau(voisin->pos()))
					calculerEssentiel(*voisin, adv, Position(-1, -1));
		}
		if(false && iAction % 2)
		{
			base = ma_base();
			vector<Position> departs;
			for(auto pos = base.begin(); pos != base.end(); ++pos)
				departs.push_back(Position(pos->x, pos->y));
			reparerEssentiel(departs);
		}
		else
		{
			printf("coin\n");
			vector<Position> myNetwork;
			for(int x = 0; x < TAILLE_TERRAIN; ++x)
				for(int y = 0; y < TAILLE_TERRAIN; ++y)
					if(grille[x][y].distBase[MOI] != oo && grille[x][y].distBase[MOI] <= grille[x][y].distBase[ADV] && (est_tuyau(Position(x,y).pos()) || type_case(Position(x,y)) == BASE))
						myNetwork.push_back(Position(x, y));
			priority_queue<Chemin> coups = reliures(myNetwork);
			if(coups.size())
			{
				Chemin coup = coups.top();
				deblayer(coup.depart.pos());
				construire(coup.depart.pos());
			}
			/*
			while(coups.size())
			{
				Chemin coup = coups.top();
				coups.pop();
				printf("%d %d / %d / %d %d\n", coup.depart.x, coup.depart.y, coup.longueur, coup.pos.x, coup.pos.y);
			}
			printf("\n");
			//*/
		}
	}
	for(int x = 0; x < TAILLE_TERRAIN; ++x)
		for(int y = 0; y < TAILLE_TERRAIN; ++y)
			if(grille[x][y].distBase[MOI] != oo && grille[x][y].distBase[MOI] < grille[x][y].distBase[ADV])
				ameliorer(Position(x, y).pos());
	auto base = ma_base();
	for(auto pos = base.begin(); pos != base.end(); ++pos)
		if(puissance_aspiration(*pos) < LIMITE_ASPIRATION)
		{
			if(baseUtile(Position(pos->x, pos->y)))
				for(int i = 2; i < 6; ++i)
					for(int iDir = 0; iDir < 4; ++iDir)
					{
						Position newPos = Position(pos->x, pos->y);
						for(int j = 0; j < i; ++j)
							newPos = newPos + dirs[iDir];
						if(type_case(newPos) == BASE && !baseUtile(newPos))
							deplacer_aspiration(newPos.pos(), *pos);
					}
		}
	//printf("%lf\n", double(clock() - debutTime) / CLOCKS_PER_SEC);
}

void partie_fin()
{
}

void calculerDistBase(int joueur)
{
	++idDejaVu;
	priority_queue<Chemin> file;
	vector<position> base = (joueur == id) ? ma_base() : base_ennemie();
	for(auto pos = base.begin(); pos != base.end(); ++pos)
		file.push(Chemin(Position(pos->x, pos->y), -puissance_aspiration(*pos)));
	while(file.size())
	{
		Chemin curPos = file.top();
		file.pop();
		if(curPos.pos.getGrille().dejaVu < idDejaVu)
		{
			grille[curPos.pos.x][curPos.pos.y].dejaVu = idDejaVu;
			grille[curPos.pos.x][curPos.pos.y].distBase[joueur == id] = curPos.longueur;
			if(type_case(curPos.pos) == BASE || est_tuyau(curPos.pos.pos()))
			{
				auto voisins = curPos.voisins();
				for(auto voisin = voisins.begin(); voisin != voisins.end(); ++voisin)
					file.push(*voisin);
			}
		}
	}
}
/*
void calculerUtilite(int joueur)
{
	for(int x = 0; x < TAILLE_TERRAIN; ++x)
		for(int y = 0; y < TAILLE_TERRAIN; ++y)
	
}
*/
priority_queue<Chemin> reliures(vector<Position> departs)
{
	bool enablePipes = true;
	auto pulsars = liste_pulsars();
	for(auto pulsar = pulsars.begin(); pulsar != pulsars.end(); ++pulsar)
		enablePipes = enablePipes && !pulsarAccessible(Position(pulsar->x, pulsar->y));
	++idDejaVu;
	queue<Chemin> file;
	priority_queue<Chemin> reponse;
	for(auto depart = departs.begin(); depart != departs.end(); ++depart)
		file.push(Chemin(*depart));
	while(file.size())
	{
		Chemin curPos = file.front();
		file.pop();
		if(curPos.pos.getGrille().dejaVu < idDejaVu)
		{
			grille[curPos.pos.x][curPos.pos.y].dejaVu = idDejaVu;
			if(curPos.longueur > 0)
			{
				curPos.score = curPos.pos.newPlasma();
				if(curPos.pos.newPlasma())
					reponse.push(curPos);
				//curPos.score = -curPos.pos.getGrille().distBase[MOI];
				//if(enablePipes && curPos.depart.x != -1 && est_tuyau(curPos.pos.pos()))
				//	reponse.push(curPos);
			}
			if(curPos.longueur == 0 || type_case(curPos.pos) == VIDE || type_case(curPos.pos) == DEBRIS || (curPos.depart.x != -1 && est_tuyau(curPos.pos.pos())))
			{
				auto voisins = curPos.voisins();
				for(auto voisin = voisins.begin(); voisin != voisins.end(); ++voisin)
					if(!est_pulsar(voisin->pos.pos()) && (!est_tuyau(voisin->pos.pos()) || voisin->pos.x != voisin->depart.x || voisin->pos.y != voisin->depart.y))
					{
						if(est_debris(voisin->pos.pos()))
							voisin->longueur += 1;
						if(est_tuyau(voisin->pos.pos()))
							voisin->longueur -= 1;
						file.push(*voisin);
					}
			}
		}
	}
	return reponse;
}

int calculerEssentiel(Position noeud, int joueur, Position pere, int prof)
{
	if(noeud.getGrille().prof[joueur == id] != -1)
		return noeud.getGrille().prof[joueur == id];
	grille[noeud.x][noeud.y].prof[joueur == id] = prof;
	grille[noeud.x][noeud.y].remontee[joueur == id] = prof;
	auto voisins = noeud.voisins();
	for(auto voisin = voisins.begin(); voisin != voisins.end(); ++voisin)
		if(est_tuyau(voisin->pos()))
		{
			++grille[noeud.x][noeud.y].nbVoisins[joueur == id];
			if(voisin->x != pere.x || voisin->y != pere.y)
			{
				int remontee = calculerEssentiel(*voisin, joueur, noeud, prof + 1);
				grille[noeud.x][noeud.y].remontee[joueur == id] = min(noeud.getGrille().remontee[joueur == id], remontee);
			}
		}
	//if(joueur == id)
	//	printf("%d %d -> %d %d %d\n", noeud.x, noeud.y, noeud.getGrille().prof[joueur == id], noeud.getGrille().remontee[joueur == id], noeud.getGrille().essentiel(joueur));
	return noeud.getGrille().remontee[joueur == id];
}

void reparerEssentiel(vector<Position> departs)
{
	++idDejaVu;
	vector<Position> essentiels;
	queue<Position> file;
	auto base = ma_base();
	for(auto pos = base.begin(); pos != base.end(); ++pos)
		if(pos->x < TAILLE_TERRAIN - 1 && pos->y < TAILLE_TERRAIN - 1)
			file.push(Position(pos->x, pos->y));
	while(file.size())
	{
		Position curPos = file.front();
		file.pop();
		if(curPos.getGrille().dejaVu < idDejaVu)
		{
			grille[curPos.x][curPos.y].dejaVu = idDejaVu;
			if(curPos.getGrille().essentiel(MOI))
				essentiels.push_back(curPos);
			else
			{
				auto voisins = curPos.voisins();
				for(auto voisin = voisins.begin(); voisin != voisins.end(); ++voisin)
					if(est_tuyau(voisin->pos()))
						file.push(*voisin);
			}
		}
	}
	if(essentiels.empty())
	{
		++idDejaVu;
		for(auto pos = base.begin(); pos != base.end(); ++pos)
			if(pos->x > 0 && pos->y > 0)
				file.push(Position(pos->x, pos->y));
		while(file.size())
		{
			Position curPos = file.front();
			file.pop();
			if(curPos.getGrille().dejaVu < idDejaVu)
			{
				grille[curPos.x][curPos.y].dejaVu = idDejaVu;
				if(curPos.getGrille().essentiel(MOI))
					essentiels.push_back(curPos);
				else
				{
					auto voisins = curPos.voisins();
					for(auto voisin = voisins.begin(); voisin != voisins.end(); ++voisin)
						if(est_tuyau(voisin->pos()))
							file.push(*voisin);
				}
			}
		}	
	}
	if(essentiels.size())
	{
		queue<Chemin> file2;
		for(int x = 0; x < TAILLE_TERRAIN; ++x)
			for(int y = 0; y < TAILLE_TERRAIN; ++y)
				if(grille[x][y].dejaVu == idDejaVu && (x != essentiels.front().x || y != essentiels.front().y))
					file2.push(Chemin(Position(x, y)));
		++idDejaVu;
		while(file2.size())
		{
			Chemin curPos = file2.front();
			file2.pop();
			if(curPos.pos.getGrille().dejaVu < idDejaVu - 1 && est_tuyau(curPos.pos.pos()))
			{
				deblayer(curPos.depart.pos());
				construire(curPos.depart.pos());
				return;
			}
			if(curPos.pos.getGrille().dejaVu < idDejaVu)
			{
				grille[curPos.pos.x][curPos.pos.y].dejaVu = idDejaVu;
				auto voisins = curPos.voisins();
				for(auto voisin = voisins.begin(); voisin != voisins.end(); ++voisin)
					if((voisin->pos.x != essentiels.front().x || voisin->pos.y != essentiels.front().y) && !est_pulsar(voisin->pos.pos()))
						file2.push(*voisin);
			}
		}
	}
}

case_type type_case(Position pos)
{
	return type_case(pos.pos());
}

int plasmaRestant(Position pulsar)
{
	pulsar_info info = info_pulsar(pulsar.pos());
	return info.pulsations_restantes * info.puissance;
}

bool pulsarAccessible(Position pulsar)
{
	auto voisins = pulsar.voisins();
	for(auto voisin = voisins.begin(); voisin != voisins.end(); ++voisin)
		if(est_libre(voisin->pos()))
			return true;
	return false;
}

bool baseUtile(Position base)
{
	auto voisins = base.voisins();
	for(auto voisin = voisins.begin(); voisin != voisins.end(); ++voisin)
		if(est_tuyau(voisin->pos()))
			return true;
	return false;
}
